﻿using System;
using System.Collections.Generic;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental;
using TSU.FAMC.ComputerEngineering.Classes.Enums;
using TSU.FAMC.ComputerEngineering.Classes.General;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions
{
    /// <summary>
    /// System of boolean functions.
    /// </summary>
    public class BooleanSystem : ICloneable
    {
        // Fields

        /// <summary>
        /// List of boolean functions.
        /// </summary>
        public List<BooleanFunction> Functions { get; set; }


        // Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public BooleanSystem()
        {
            Functions = new List<BooleanFunction>();
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="booleanSystem"></param>
        public BooleanSystem(BooleanSystem booleanSystem)
        {
            // copying values
            Functions = booleanSystem.Functions;
        }

        /// <summary>
        /// Clones boolean system in terms of boolean functions containing the system.
        /// </summary>
        /// <returns>System's clone.</returns>
        public object Clone()
        {
            var clone = new BooleanSystem(this);
            return clone;
        }

        /// <summary>
        /// Returns hash code for boolean system.
        /// </summary>
        /// <returns>Integer hash code.</returns>
        public override int GetHashCode()
        {
            return Functions.GetHashCode();
        }

        /// <summary>
        /// Checks whether two boolean systems are equal.
        /// </summary>
        /// <param name="booleanSystem">Boolean system to compare with.</param>
        /// <returns>True if systems are equal.</returns>
        public bool Equals(BooleanSystem booleanSystem)
        {
            if (ReferenceEquals(null, booleanSystem)) return false;
            if (ReferenceEquals(this, booleanSystem) ||
                ReferenceEquals(Functions, booleanSystem.Functions))
            {
                return true;
            }

            if (Functions.Count != booleanSystem.Functions.Count)
            {
                return false;
            }

            return !Functions.Where((t, i) => t != booleanSystem.Functions[i]).Any();
        }

        /// <summary>
        /// Checks whether two objects are equal.
        /// </summary>
        /// <param name="obj">Object to compare with.</param>
        /// <returns>True if objects are equal.</returns>
        public override bool Equals(object obj)
        {
            if (obj is BooleanSystem)
            {
                return Equals(obj as BooleanSystem);
            }

            return base.Equals(obj);
        }


        /// <summary>
        /// Checks value equality of two boolean systems.
        /// </summary>
        /// <param name="s1">First boolean system.</param>
        /// <param name="s2">Second boolean system.</param>
        /// <returns>True if systems are equal in terms of its values.</returns>
        public static bool operator ==(BooleanSystem s1, BooleanSystem s2)
        {
            return !ReferenceEquals(s1, null) && s1.Equals(s2);
        }

        /// <summary>
        /// Checks value inequality of two boolean systems.
        /// </summary>
        /// <param name="s1">First boolean system.</param>
        /// <param name="s2">Second boolean BooleanSystem.</param>
        /// <returns>True if systems are not equal in terms of its values.</returns>
        public static bool operator !=(BooleanSystem s1, BooleanSystem s2)
        {
            return !(s1 == s2);
        }

        /// <summary>
        /// Checks system for consistency.
        /// </summary>
        /// <returns>True if the system is correct.</returns>
        public bool CheckConsistency()
        {
            return Functions.All(function => function.CheckConsistency());
        }

        /// <summary>
        /// Minimizes boolean system within method of competitive intervals (Zakrevsky).
        /// </summary>
        /// <returns>Minimized boolean system.</returns>
        public BooleanSystem Minimize()
        {
            //------------------------------------------------
            // Algorithm of competitive intervals (Zakrevsky). 
            //------------------------------------------------

            // Checkings

            if (!CheckConsistency())
            {
                throw new InvalidOperationException("System consistency checking failed!");
            }


            // Preparation

            // building rows data
            var rowsData = new List<CharObject>();
            for (var i = 0; i < Functions.Count; i++)
            {
                // going over domain 1
                foreach (var cube in Functions[i].Domain1)
                {
                    // preparing function's characteristic
                    var chr = new BooleanVector(Functions.Count);
                    chr[i] = BooleanValue.True;

                    // creating characteristic cube
                    var cubeClone = cube.Clone() as Cube;
                    var charObject = new CharObject { MasterObject = cubeClone, Characteristic = chr };
                    rowsData.Add(charObject);
                }
            }

            // compressing matrix uniting identical cubes
            for (var i = 0; i < rowsData.Count; i++)
            {
                var cube1 = rowsData[i].MasterObject as Cube;
                for (var j = i + 1; j < rowsData.Count; j++)
                {
                    var cube2 = rowsData[j].MasterObject as Cube;

                    // comparing cubes
                    if (cube1 != cube2)
                    {
                        continue;
                    }

                    // cubes are identical, uniting them by applying bitwise OR operation to their characteristics
                    rowsData[i].Characteristic = rowsData[i].Characteristic | rowsData[j].Characteristic;

                    // deleting cube
                    rowsData.RemoveAt(j);
                    j--;
                }
            }

            // creating system matrix
            var matrix = new BooleanMatrix {RowsCount = rowsData.Count};

            // instantiating columns data
            var columnsData = new List<CharObject>();

            // result data
            var resultData = new List<CharObject>();


            //------------------------
            // Algorithm's iterations.
            //------------------------

            while (rowsData.Count != 0 || columnsData.Count != 0)
            {
                // Step 1. Until empty rows exist in matrix, move them into columns and transform matrix.
                for (var i = 0; i < matrix.RowsCount; i++)
                {
                    if (matrix.RowWeight(i) != 0)
                    {
                        continue;
                    }

                    // empty row

                    // adding data associated with current row into the new matrix column and deleting row
                    var columnData = rowsData[i];
                    columnsData.Add(columnData);
                    
                    // deleting row
                    rowsData.RemoveAt(i);
                    matrix.DelRowAt(i);

                    // adding new column
                    matrix.AddColumn();

                    // calculating new column matrix data
                    for (var j = 0; j < matrix.RowsCount; j++)
                    {
                        // building minimum covering cube (closure) for column and row cubes
                        var rowData = rowsData[j];
                        var rowCube = (Cube) rowData.MasterObject;
                        var columnCube = (Cube) columnData.MasterObject;
                        var closure = rowCube.Closure(columnCube);

                        // checking whether closure intersects with domain 0 of 
                        // any functions from union of row and column characteristics.
                        var resultChr = rowData.Characteristic | columnData.Characteristic;
                        var isIntersect = Functions.Where((t, k) => resultChr[k] == BooleanValue.True).Any(t => !t.Domain0.IsOrthogonalTo(closure));

                        if (!isIntersect)
                        {
                            // no intersection
                            matrix[j, matrix.ColumnsCount - 1] = BooleanValue.True;
                        }
                    }

                    // reseting i
                    i = -1;
                }

                
                // Step 2 (empty column). Expanding column cube to the Domain1 and Domain- of column's characteristics and
                // adding result of epansion to the result.
                var executed = false;
                for (var i = 0; i < matrix.ColumnsCount; i++)
                {
                    if (matrix.ColumnWeight(i) != 0)
                    {
                        continue;
                    }

                    // empty column
                    var columnData = columnsData[i];
                    var columnCube = (Cube)columnData.MasterObject;
                    var chr = columnData.Characteristic;
                    
                    // trying expand cube within the scope of the system
                    for (var j = 0; j < columnCube.Length; j++)
                    {
                        var value = columnCube[j];
                        if (value == BooleanValue.Undefined)
                        {
                            continue;
                        }

                        // trying expand cube
                        columnCube[j] = BooleanValue.Undefined;

                        // checking correctness within the scope of the system
                        for (var k = 0; k < chr.Length; k++)
                        {
                            if (chr[k] == BooleanValue.False)
                            {
                                continue;
                            }

                            if (!Functions[k].Domain0.IsOrthogonalTo(columnCube))
                            {
                                // system correctness was failed
                                
                                // returning old value of cube bit
                                columnCube[j] = value;
                                break;
                            }
                        }
                    }

                    // including column data into the result dataset
                    resultData.Add(columnData);

                    // deleting column
                    matrix.DelColumnAt(i);
                    columnsData.RemoveAt(i);

                    // updating index to prevent next column skipping
                    executed = true;
                    i--;
                }

                if (executed)
                {
                    // trying execute previous steps
                    continue;
                }

                // Step 3 (column with one true bit). Building column and row cubes closure and trying expand it
                // within the scope of the system.
                for (var i = 0; i < matrix.ColumnsCount; i++)
                {
                    if (matrix.ColumnWeight(i) != 1)
                    {
                        continue;
                    }

                    // 1-weighted column

                    // searching for corresponding row
                    var column = matrix.Columns[i];
                    var rowIdx = -1;
                    for (var j = 0; j < column.Length; j++)
                    {
                        if (column[j] != BooleanValue.True)
                        {
                            continue;
                        }

                        rowIdx = j;
                        break;
                    }

                    // retrieving data 
                    var columnData = columnsData[i];
                    var columnCube = (Cube)columnData.MasterObject;

                    var rowData = rowsData[rowIdx];
                    var rowCube = (Cube)rowData.MasterObject;

                    var resultChr = columnData.Characteristic | rowData.Characteristic;

                    // building closure
                    var closureCube = columnCube.Closure(rowCube);

                    // trying expand cube within the scope of the system
                    for (var j = 0; j < closureCube.Length; j++)
                    {
                        var value = closureCube[j];
                        if (value == BooleanValue.Undefined)
                        {
                            continue;
                        }

                        // trying expand cube
                        closureCube[j] = BooleanValue.Undefined;

                        // checking correctness within the scope of the system
                        for (var k = 0; k < resultChr.Length; k++)
                        {
                            if (resultChr[k] == BooleanValue.False)
                            {
                                continue;
                            }

                            if (!Functions[k].Domain0.IsOrthogonalTo(closureCube))
                            {
                                // system correctness was failed

                                // returning old value of cube bit
                                closureCube[j] = value;
                                break;
                            }
                        }
                    }

                    // including column data into the result dataset
                    var closureData = new CharObject { MasterObject = closureCube, Characteristic = resultChr };
                    resultData.Add(closureData);

                    // deleting column
                    matrix.DelColumnAt(i);
                    columnsData.RemoveAt(i);

                    // updating index to prevent next column skipping
                    executed = true;
                    i--;
                }

                if (executed)
                {
                    // trying execute previous steps
                    continue;
                }

                if (matrix.ColumnsCount == 0 || matrix.RowsCount == 0)
                {
                    // no one column, trying execute step 1
                    continue;
                }


                // Step 4. Searching for a cube with the maximum rank in columns data and 
                // expanding it to cover the nearest cube from the rows.

                // searching for a cube with the maximum rank
                var maxRankIdx = -1;
                var maxRank = 0;
                for (var i = 0; i < matrix.ColumnsCount; i++)
                {
                    var cube = (Cube)columnsData[i].MasterObject;
                    if (cube.Rank <= maxRank)
                    {
                        continue;
                    }

                    maxRank = cube.Rank;
                    maxRankIdx = i;
                }

                // retrieving column data
                var maxRankColumnData = columnsData[maxRankIdx];
                var maxRankColumnCube = (Cube)maxRankColumnData.MasterObject;

                // searching for the nearest (minimum distance) covered cube in rows
                var minDistance = maxRankColumnCube.Length;
                var minDistanceIdx = -1;
                for (var i = 0; i < matrix.RowsCount; i++)
                {
                    if (matrix[i, maxRankIdx] != BooleanValue.True)
                    {
                        continue;
                    }

                    // covered row cube
                    var cube = (Cube)rowsData[i].MasterObject;
                    if (cube.Distance(maxRankColumnCube) < minDistance)
                    {
                        minDistance = cube.Distance(maxRankColumnCube);
                        minDistanceIdx = i;
                    }
                }

                // retrieving  row data
                var minDistanceRowData = rowsData[minDistanceIdx];
                var minDistanceCube = (Cube)minDistanceRowData.MasterObject;

                // building closure
                var expandedCube = maxRankColumnCube.Closure(minDistanceCube);
                var expandedChr = maxRankColumnData.Characteristic | minDistanceRowData.Characteristic;

                // setting closure cube to the corresponding column
                columnsData[maxRankIdx].MasterObject = expandedCube;
                columnsData[maxRankIdx].Characteristic = expandedChr;

                // deleting all rows cubes which are covered by the closure
                for (var i = 0; i < matrix.RowsCount; i++)
                {
                    if (matrix[i, maxRankIdx] != BooleanValue.True)
                    {
                        continue;
                    }

                    var cube = (Cube)rowsData[i].MasterObject;
                    var closureCubeSet = new CubeSet {expandedCube};
                    if (!cube.IsCoveredBy(closureCubeSet))
                    {
                        continue;
                    }

                    // deleting row
                    rowsData.RemoveAt(i);
                    matrix.DelRowAt(i);

                    // updating index
                    i--;
                }

                // calculating relations in column
                for (var j = 0; j < matrix.RowsCount; j++)
                {
                    // building minimum covering cube (closure) for column and row cubes
                    var rowData = rowsData[j];
                    var rowCube = (Cube)rowData.MasterObject;
                    var closure = rowCube.Closure(expandedCube);

                    // checking whether closure intersects with domain 0 of 
                    // any functions from union of row and column characteristics.
                    var resultChr = rowData.Characteristic | expandedChr;
                    var isIntersect = Functions.Where((t, k) => resultChr[k] == BooleanValue.True).Any(t => !t.Domain0.IsOrthogonalTo(closure));

                    if (!isIntersect)
                    {
                        // no intersection
                        matrix[j, maxRankIdx] = BooleanValue.True;
                    }
                    else
                    {
                        matrix[j, maxRankIdx] = BooleanValue.False;
                    }
                }
            }

            // building resulting boolean system
            var result = new BooleanSystem();

            // building functions
            for (var i = 0; i < Functions.Count; i++)
            {
                var function = new BooleanFunction();

                // building Domain1 of the function
                var domain1 = new CubeSet();
                foreach (var t in resultData)
                {
                    if (t.Characteristic[i] != BooleanValue.True)
                    {
                        continue;
                    }

                    var cube = (Cube)t.MasterObject;
                    domain1.Add(cube);
                }

                // setting Domain1
                function.Domain1 = domain1;

                // checking for irredandancy Domain1
                var cubesToRemove = new CubeSet();
                do
                {
                    // deleting redundant cubes
                    foreach (var cube in cubesToRemove)
                    {
                        function.Domain1.Remove(cube);
                    }
                    cubesToRemove.Clear();

                    foreach (var cube in function.Domain1)
                    {
                        var otherCubes = new CubeSet(function.Domain1);
                        otherCubes.Remove(cube);

                        if (!cube.IsCoveredBy(otherCubes))
                        {
                            continue;
                        }

                        // cube is redundant, memorizing it
                        cubesToRemove.Add(cube);
                        break;
                    }
                }
                while (cubesToRemove.Count != 0);

                // copying Domain0
                function.Domain0 = Functions[i].Domain0;

                // checking for irredandancy Domain0
                cubesToRemove.Clear();
                do
                {
                    // deleting redundant cubes
                    foreach (var cube in cubesToRemove)
                    {
                        function.Domain0.Remove(cube);
                    }
                    cubesToRemove.Clear();

                    foreach (var cube in function.Domain0)
                    {
                        var otherCubes = new CubeSet(function.Domain0);
                        otherCubes.Remove(cube);

                        if (!cube.IsCoveredBy(otherCubes))
                        {
                            continue;
                        }

                        // cube is redundant, memorizing it
                        cubesToRemove.Add(cube);
                        break;
                    }
                }
                while (cubesToRemove.Count != 0);

                // adding function to the result
                result.Functions.Add(function);
            }


            // checking whether result system is realization of the initial one
            if (!result.IsRealizationOf(this))
            {
                throw new Exception("Something was wrong because minimized system is no a realization of initial one! Check the algorithm!");
            }

            return result;
        }

        /// <summary>
        /// Minimizes each function in the system separately.
        /// </summary>
        /// <returns>Minimized boolean system, when each function minimized separately.</returns>
        public BooleanSystem MinimizeEach()
        {
            var result = new BooleanSystem();

            foreach (var function in Functions)
            {
                // bulding system of one function to minimize
                var funcSystem = new BooleanSystem();
                funcSystem.Functions.Add(function);

                // minimizing system
                var funcSystemMinimized = funcSystem.Minimize();

                // saving result
                result.Functions.Add(funcSystemMinimized.Functions[0]);
            }

            // checking whether result system is realization of the initial one
            if (!result.IsRealizationOf(this))
            {
                throw new Exception("Something was wrong because minimized system is no a realization of initial one! Check the algorithm!");
            }

            return result;
        }

        /// <summary>
        /// Checks whether new system is realization of specified one.
        /// </summary>
        /// <param name="target">Boolean system object to compare to.</param>
        /// <returns>True if current system is realization of specified one.</returns>
        public bool IsRealizationOf(BooleanSystem target)
        {
            if (Functions.Count != target.Functions.Count)
            {
                return false;
            }

            return !Functions.Where((t, i) => !t.IsRealizationOf(target.Functions[i])).Any();
        }

        /// <summary>
        /// Checks whether each function of boolean system is irredundent.
        /// </summary>
        /// <returns>True if current system is irredundant.</returns>
        public bool TestIrredundency()
        {
            //----------------------------------------------------------------------------------
            //  1. Checking Domain1 and Domain0 of each function of the system for irredundancy.
            //----------------------------------------------------------------------------------

            foreach (var function in Functions)
            {
                // checking for irredandancy Domain1
                foreach (var cube in function.Domain1)
                {
                    var otherCubes = new CubeSet(function.Domain1);
                    otherCubes.Remove(cube);

                    if (!cube.IsCoveredBy(otherCubes))
                    {
                        continue;
                    }

                    // Irredundancy test failed!
                    return false;
                }

                // checking for irredandancy Domain0
                foreach (var cube in function.Domain0)
                {
                    var otherCubes = new CubeSet(function.Domain0);
                    otherCubes.Remove(cube);

                    if (!cube.IsCoveredBy(otherCubes))
                    {
                        continue;
                    }

                    // Irredundancy test failed!
                    return false;
                }
            }


            //----------------------------------------------------------------------------------
            //  2. Checking variables in function conjunctions for irredundancy.
            //----------------------------------------------------------------------------------

            foreach (var function in Functions)
            {
                foreach (var cube in function.Domain1)
                {
                    // tersting each determined variable in cube for expansion
                    for (var i = 0; i < cube.Dimension; i++)
                    {
                        if (cube[i] == BooleanValue.Undefined)
                        {
                            // indetermined variable
                            continue;
                        }

                        var savedValue = cube[i];

                        // trying variable expansion
                        cube[i] = BooleanValue.Undefined;

                        // testing expanded cube for intersection with Domain0
                        if (!cube.TestIntersection(function.Domain0))
                        {
                            // current cube can be expanded for current function

                            // Irredundancy test failed!
                            return false;
                        }

                        // reseting value
                        cube[i] = savedValue;
                    }
                }
            }

            return true;
        }
    }
}
